#include "stdafx.h"

//--------------------------------------------------------------------------------------
// File: DDSTextureLoader.cpp
//
// Functions for loading a DDS texture and creating a Direct3D runtime resource for it
//
// Note these functions are useful as a light-weight runtime loader for DDS files. For
// a full-featured DDS file reader, writer, and texture processing pipeline see
// the 'Texconv' sample and the 'DirectXTex' library.
//
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
//
// http://go.microsoft.com/fwlink/?LinkId=248926
// http://go.microsoft.com/fwlink/?LinkId=248929
//--------------------------------------------------------------------------------------

#include "DDSTextureLoader.h"

#include <assert.h>
#include <algorithm>
#include <memory>

#if !defined(NO_D3D11_DEBUG_NAME) && ( defined(_DEBUG) || defined(PROFILE) )
#pragma comment(lib,"dxguid.lib")
#endif

using namespace DirectX;

//--------------------------------------------------------------------------------------
// Macros
//--------------------------------------------------------------------------------------
#ifndef MAKEFOURCC
#define MAKEFOURCC(ch0, ch1, ch2, ch3)                              \
                ((uint32_t)(uint8_t)(ch0) | ((uint32_t)(uint8_t)(ch1) << 8) |       \
                ((uint32_t)(uint8_t)(ch2) << 16) | ((uint32_t)(uint8_t)(ch3) << 24 ))
#endif /* defined(MAKEFOURCC) */

//--------------------------------------------------------------------------------------
// DDS file structure definitions
//
// See DDS.h in the 'Texconv' sample and the 'DirectXTex' library
//--------------------------------------------------------------------------------------
#pragma pack(push,1)

const uint32_t DDS_MAGIC = 0x20534444; // "DDS "

struct DDS_PIXELFORMAT
{
	uint32_t    size;
	uint32_t    flags;
	uint32_t    fourCC;
	uint32_t    RGBBitCount;
	uint32_t    RBitMask;
	uint32_t    GBitMask;
	uint32_t    BBitMask;
	uint32_t    ABitMask;
};

#define DDS_FOURCC      0x00000004  // DDPF_FOURCC
#define DDS_RGB         0x00000040  // DDPF_RGB
#define DDS_LUMINANCE   0x00020000  // DDPF_LUMINANCE
#define DDS_ALPHA       0x00000002  // DDPF_ALPHA
#define DDS_BUMPDUDV    0x00080000  // DDPF_BUMPDUDV

#define DDS_HEADER_FLAGS_VOLUME         0x00800000  // DDSD_DEPTH

#define DDS_HEIGHT 0x00000002 // DDSD_HEIGHT
#define DDS_WIDTH  0x00000004 // DDSD_WIDTH

#define DDS_CUBEMAP_POSITIVEX 0x00000600 // DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_POSITIVEX
#define DDS_CUBEMAP_NEGATIVEX 0x00000a00 // DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_NEGATIVEX
#define DDS_CUBEMAP_POSITIVEY 0x00001200 // DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_POSITIVEY
#define DDS_CUBEMAP_NEGATIVEY 0x00002200 // DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_NEGATIVEY
#define DDS_CUBEMAP_POSITIVEZ 0x00004200 // DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_POSITIVEZ
#define DDS_CUBEMAP_NEGATIVEZ 0x00008200 // DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_NEGATIVEZ

#define DDS_CUBEMAP_ALLFACES ( DDS_CUBEMAP_POSITIVEX | DDS_CUBEMAP_NEGATIVEX |\
                               DDS_CUBEMAP_POSITIVEY | DDS_CUBEMAP_NEGATIVEY |\
                               DDS_CUBEMAP_POSITIVEZ | DDS_CUBEMAP_NEGATIVEZ )

#define DDS_CUBEMAP 0x00000200 // DDSCAPS2_CUBEMAP

enum DDS_MISC_FLAGS2
{
	DDS_MISC_FLAGS2_ALPHA_MODE_MASK = 0x7L,
};

struct DDS_HEADER
{
	uint32_t        size;
	uint32_t        flags;
	uint32_t        height;
	uint32_t        width;
	uint32_t        pitchOrLinearSize;
	uint32_t        depth; // only if DDS_HEADER_FLAGS_VOLUME is set in flags
	uint32_t        mipMapCount;
	uint32_t        reserved1[11];
	DDS_PIXELFORMAT ddspf;
	uint32_t        caps;
	uint32_t        caps2;
	uint32_t        caps3;
	uint32_t        caps4;
	uint32_t        reserved2;
};

struct DDS_HEADER_DXT10
{
	DXGI_FORMAT     dxgiFormat;
	uint32_t        resourceDimension;
	uint32_t        miscFlag; // see D3D11_RESOURCE_MISC_FLAG
	uint32_t        arraySize;
	uint32_t        miscFlags2;
};

#pragma pack(pop)

//--------------------------------------------------------------------------------------
namespace
{
	struct handle_closer { void operator()(HANDLE h) { if (h) CloseHandle(h); } };

	typedef std::unique_ptr<void, handle_closer> ScopedHandle;

	inline HANDLE safe_handle(HANDLE h) { return (h == INVALID_HANDLE_VALUE) ? nullptr : h; }

	template<UINT TNameLength>
	inline void SetDebugObjectName(_In_ ID3D11DeviceChild* resource, _In_ const char(&name)[TNameLength])
	{
#if defined(_DEBUG) || defined(PROFILE)
		resource->SetPrivateData(WKPDID_D3DDebugObjectName, TNameLength - 1, name);
#else
		UNREFERENCED_PARAMETER(resource);
		UNREFERENCED_PARAMETER(name);
#endif
	}

	//--------------------------------------------------------------------------------------
	HRESULT LoadTextureDataFromFile(
		_In_z_ const wchar_t* fileName,
		std::unique_ptr<uint8_t[]>& ddsData,
		const DDS_HEADER** header,
		const uint8_t** bitData,
		size_t* bitSize)
	{
		if (!header || !bitData || !bitSize)
		{
			return E_POINTER;
		}

		// open the file
#if (_WIN32_WINNT >= _WIN32_WINNT_WIN8)
		ScopedHandle hFile(safe_handle(CreateFile2(fileName,
			GENERIC_READ,
			FILE_SHARE_READ,
			OPEN_EXISTING,
			nullptr)));
#else
		ScopedHandle hFile(safe_handle(CreateFileW(fileName,
			GENERIC_READ,
			FILE_SHARE_READ,
			nullptr,
			OPEN_EXISTING,
			FILE_ATTRIBUTE_NORMAL,
			nullptr)));
#endif

		if (!hFile)
		{
			return HRESULT_FROM_WIN32(GetLastError());
		}

		// Get the file size
		FILE_STANDARD_INFO fileInfo;
		if (!GetFileInformationByHandleEx(hFile.get(), FileStandardInfo, &fileInfo, sizeof(fileInfo)))
		{
			return HRESULT_FROM_WIN32(GetLastError());
		}

		// File is too big for 32-bit allocation, so reject read
		if (fileInfo.EndOfFile.HighPart > 0)
		{
			return E_FAIL;
		}

		// Need at least enough data to fill the header and magic number to be a valid DDS
		if (fileInfo.EndOfFile.LowPart < (sizeof(DDS_HEADER) + sizeof(uint32_t)))
		{
			return E_FAIL;
		}

		// create enough space for the file data
		ddsData.reset(new (std::nothrow) uint8_t[fileInfo.EndOfFile.LowPart]);
		if (!ddsData)
		{
			return E_OUTOFMEMORY;
		}

		// read the data in
		DWORD BytesRead = 0;
		if (!ReadFile(hFile.get(),
			ddsData.get(),
			fileInfo.EndOfFile.LowPart,
			&BytesRead,
			nullptr
		))
		{
			return HRESULT_FROM_WIN32(GetLastError());
		}

		if (BytesRead < fileInfo.EndOfFile.LowPart)
		{
			return E_FAIL;
		}

		// DDS files always start with the same magic number ("DDS ")
		uint32_t dwMagicNumber = *reinterpret_cast<const uint32_t*>(ddsData.get());
		if (dwMagicNumber != DDS_MAGIC)
		{
			return E_FAIL;
		}

		auto hdr = reinterpret_cast<const DDS_HEADER*>(ddsData.get() + sizeof(uint32_t));

		// Verify header to validate DDS file
		if (hdr->size != sizeof(DDS_HEADER) ||
			hdr->ddspf.size != sizeof(DDS_PIXELFORMAT))
		{
			return E_FAIL;
		}

		// Check for DX10 extension
		bool bDXT10Header = false;
		if ((hdr->ddspf.flags & DDS_FOURCC) &&
			(MAKEFOURCC('D', 'X', '1', '0') == hdr->ddspf.fourCC))
		{
			// Must be long enough for both headers and magic value
			if (fileInfo.EndOfFile.LowPart < (sizeof(DDS_HEADER) + sizeof(uint32_t) + sizeof(DDS_HEADER_DXT10)))
			{
				return E_FAIL;
			}

			bDXT10Header = true;
		}

		// setup the pointers in the process request
		*header = hdr;
		ptrdiff_t offset = sizeof(uint32_t) + sizeof(DDS_HEADER)
			+ (bDXT10Header ? sizeof(DDS_HEADER_DXT10) : 0);
		*bitData = ddsData.get() + offset;
		*bitSize = fileInfo.EndOfFile.LowPart - offset;

		return S_OK;
	}


	//--------------------------------------------------------------------------------------
	// Return the BPP for a particular format
	//--------------------------------------------------------------------------------------
	size_t BitsPerPixel(_In_ DXGI_FORMAT fmt)
	{
		switch (fmt)
		{
		case DXGI_FORMAT_R32G32B32A32_TYPELESS:
		case DXGI_FORMAT_R32G32B32A32_FLOAT:
		case DXGI_FORMAT_R32G32B32A32_UINT:
		case DXGI_FORMAT_R32G32B32A32_SINT:
			return 128;

		case DXGI_FORMAT_R32G32B32_TYPELESS:
		case DXGI_FORMAT_R32G32B32_FLOAT:
		case DXGI_FORMAT_R32G32B32_UINT:
		case DXGI_FORMAT_R32G32B32_SINT:
			return 96;

		case DXGI_FORMAT_R16G16B16A16_TYPELESS:
		case DXGI_FORMAT_R16G16B16A16_FLOAT:
		case DXGI_FORMAT_R16G16B16A16_UNORM:
		case DXGI_FORMAT_R16G16B16A16_UINT:
		case DXGI_FORMAT_R16G16B16A16_SNORM:
		case DXGI_FORMAT_R16G16B16A16_SINT:
		case DXGI_FORMAT_R32G32_TYPELESS:
		case DXGI_FORMAT_R32G32_FLOAT:
		case DXGI_FORMAT_R32G32_UINT:
		case DXGI_FORMAT_R32G32_SINT:
		case DXGI_FORMAT_R32G8X24_TYPELESS:
		case DXGI_FORMAT_D32_FLOAT_S8X24_UINT:
		case DXGI_FORMAT_R32_FLOAT_X8X24_TYPELESS:
		case DXGI_FORMAT_X32_TYPELESS_G8X24_UINT:
		case DXGI_FORMAT_Y416:
		case DXGI_FORMAT_Y210:
		case DXGI_FORMAT_Y216:
			return 64;

		case DXGI_FORMAT_R10G10B10A2_TYPELESS:
		case DXGI_FORMAT_R10G10B10A2_UNORM:
		case DXGI_FORMAT_R10G10B10A2_UINT:
		case DXGI_FORMAT_R11G11B10_FLOAT:
		case DXGI_FORMAT_R8G8B8A8_TYPELESS:
		case DXGI_FORMAT_R8G8B8A8_UNORM:
		case DXGI_FORMAT_R8G8B8A8_UNORM_SRGB:
		case DXGI_FORMAT_R8G8B8A8_UINT:
		case DXGI_FORMAT_R8G8B8A8_SNORM:
		case DXGI_FORMAT_R8G8B8A8_SINT:
		case DXGI_FORMAT_R16G16_TYPELESS:
		case DXGI_FORMAT_R16G16_FLOAT:
		case DXGI_FORMAT_R16G16_UNORM:
		case DXGI_FORMAT_R16G16_UINT:
		case DXGI_FORMAT_R16G16_SNORM:
		case DXGI_FORMAT_R16G16_SINT:
		case DXGI_FORMAT_R32_TYPELESS:
		case DXGI_FORMAT_D32_FLOAT:
		case DXGI_FORMAT_R32_FLOAT:
		case DXGI_FORMAT_R32_UINT:
		case DXGI_FORMAT_R32_SINT:
		case DXGI_FORMAT_R24G8_TYPELESS:
		case DXGI_FORMAT_D24_UNORM_S8_UINT:
		case DXGI_FORMAT_R24_UNORM_X8_TYPELESS:
		case DXGI_FORMAT_X24_TYPELESS_G8_UINT:
		case DXGI_FORMAT_R9G9B9E5_SHAREDEXP:
		case DXGI_FORMAT_R8G8_B8G8_UNORM:
		case DXGI_FORMAT_G8R8_G8B8_UNORM:
		case DXGI_FORMAT_B8G8R8A8_UNORM:
		case DXGI_FORMAT_B8G8R8X8_UNORM:
		case DXGI_FORMAT_R10G10B10_XR_BIAS_A2_UNORM:
		case DXGI_FORMAT_B8G8R8A8_TYPELESS:
		case DXGI_FORMAT_B8G8R8A8_UNORM_SRGB:
		case DXGI_FORMAT_B8G8R8X8_TYPELESS:
		case DXGI_FORMAT_B8G8R8X8_UNORM_SRGB:
		case DXGI_FORMAT_AYUV:
		case DXGI_FORMAT_Y410:
		case DXGI_FORMAT_YUY2:
			return 32;

		case DXGI_FORMAT_P010:
		case DXGI_FORMAT_P016:
			return 24;

		case DXGI_FORMAT_R8G8_TYPELESS:
		case DXGI_FORMAT_R8G8_UNORM:
		case DXGI_FORMAT_R8G8_UINT:
		case DXGI_FORMAT_R8G8_SNORM:
		case DXGI_FORMAT_R8G8_SINT:
		case DXGI_FORMAT_R16_TYPELESS:
		case DXGI_FORMAT_R16_FLOAT:
		case DXGI_FORMAT_D16_UNORM:
		case DXGI_FORMAT_R16_UNORM:
		case DXGI_FORMAT_R16_UINT:
		case DXGI_FORMAT_R16_SNORM:
		case DXGI_FORMAT_R16_SINT:
		case DXGI_FORMAT_B5G6R5_UNORM:
		case DXGI_FORMAT_B5G5R5A1_UNORM:
		case DXGI_FORMAT_A8P8:
		case DXGI_FORMAT_B4G4R4A4_UNORM:
			return 16;

		case DXGI_FORMAT_NV12:
		case DXGI_FORMAT_420_OPAQUE:
		case DXGI_FORMAT_NV11:
			return 12;

		case DXGI_FORMAT_R8_TYPELESS:
		case DXGI_FORMAT_R8_UNORM:
		case DXGI_FORMAT_R8_UINT:
		case DXGI_FORMAT_R8_SNORM:
		case DXGI_FORMAT_R8_SINT:
		case DXGI_FORMAT_A8_UNORM:
		case DXGI_FORMAT_AI44:
		case DXGI_FORMAT_IA44:
		case DXGI_FORMAT_P8:
			return 8;

		case DXGI_FORMAT_R1_UNORM:
			return 1;

		case DXGI_FORMAT_BC1_TYPELESS:
		case DXGI_FORMAT_BC1_UNORM:
		case DXGI_FORMAT_BC1_UNORM_SRGB:
		case DXGI_FORMAT_BC4_TYPELESS:
		case DXGI_FORMAT_BC4_UNORM:
		case DXGI_FORMAT_BC4_SNORM:
			return 4;

		case DXGI_FORMAT_BC2_TYPELESS:
		case DXGI_FORMAT_BC2_UNORM:
		case DXGI_FORMAT_BC2_UNORM_SRGB:
		case DXGI_FORMAT_BC3_TYPELESS:
		case DXGI_FORMAT_BC3_UNORM:
		case DXGI_FORMAT_BC3_UNORM_SRGB:
		case DXGI_FORMAT_BC5_TYPELESS:
		case DXGI_FORMAT_BC5_UNORM:
		case DXGI_FORMAT_BC5_SNORM:
		case DXGI_FORMAT_BC6H_TYPELESS:
		case DXGI_FORMAT_BC6H_UF16:
		case DXGI_FORMAT_BC6H_SF16:
		case DXGI_FORMAT_BC7_TYPELESS:
		case DXGI_FORMAT_BC7_UNORM:
		case DXGI_FORMAT_BC7_UNORM_SRGB:
			return 8;

		default:
			return 0;
		}
	}


	//--------------------------------------------------------------------------------------
	// Get surface information for a particular format
	//--------------------------------------------------------------------------------------
	HRESULT GetSurfaceInfo(
		_In_ size_t width,
		_In_ size_t height,
		_In_ DXGI_FORMAT fmt,
		size_t* outNumBytes,
		_Out_opt_ size_t* outRowBytes,
		_Out_opt_ size_t* outNumRows)
	{
		uint64_t numBytes = 0;
		uint64_t rowBytes = 0;
		uint64_t numRows = 0;

		bool bc = false;
		bool packed = false;
		bool planar = false;
		size_t bpe = 0;
		switch (fmt)
		{
		case DXGI_FORMAT_BC1_TYPELESS:
		case DXGI_FORMAT_BC1_UNORM:
		case DXGI_FORMAT_BC1_UNORM_SRGB:
		case DXGI_FORMAT_BC4_TYPELESS:
		case DXGI_FORMAT_BC4_UNORM:
		case DXGI_FORMAT_BC4_SNORM:
			bc = true;
			bpe = 8;
			break;

		case DXGI_FORMAT_BC2_TYPELESS:
		case DXGI_FORMAT_BC2_UNORM:
		case DXGI_FORMAT_BC2_UNORM_SRGB:
		case DXGI_FORMAT_BC3_TYPELESS:
		case DXGI_FORMAT_BC3_UNORM:
		case DXGI_FORMAT_BC3_UNORM_SRGB:
		case DXGI_FORMAT_BC5_TYPELESS:
		case DXGI_FORMAT_BC5_UNORM:
		case DXGI_FORMAT_BC5_SNORM:
		case DXGI_FORMAT_BC6H_TYPELESS:
		case DXGI_FORMAT_BC6H_UF16:
		case DXGI_FORMAT_BC6H_SF16:
		case DXGI_FORMAT_BC7_TYPELESS:
		case DXGI_FORMAT_BC7_UNORM:
		case DXGI_FORMAT_BC7_UNORM_SRGB:
			bc = true;
			bpe = 16;
			break;

		case DXGI_FORMAT_R8G8_B8G8_UNORM:
		case DXGI_FORMAT_G8R8_G8B8_UNORM:
		case DXGI_FORMAT_YUY2:
			packed = true;
			bpe = 4;
			break;

		case DXGI_FORMAT_Y210:
		case DXGI_FORMAT_Y216:
			packed = true;
			bpe = 8;
			break;

		case DXGI_FORMAT_NV12:
		case DXGI_FORMAT_420_OPAQUE:
			planar = true;
			bpe = 2;
			break;

		case DXGI_FORMAT_P010:
		case DXGI_FORMAT_P016:
			planar = true;
			bpe = 4;
			break;

		default:
			break;
		}

		if (bc)
		{
			uint64_t numBlocksWide = 0;
			if (width > 0)
			{
				numBlocksWide = std::max<uint64_t>(1u, (uint64_t(width) + 3u) / 4u);
			}
			uint64_t numBlocksHigh = 0;
			if (height > 0)
			{
				numBlocksHigh = std::max<uint64_t>(1u, (uint64_t(height) + 3u) / 4u);
			}
			rowBytes = numBlocksWide * bpe;
			numRows = numBlocksHigh;
			numBytes = rowBytes * numBlocksHigh;
		}
		else if (packed)
		{
			rowBytes = ((uint64_t(width) + 1u) >> 1) * bpe;
			numRows = uint64_t(height);
			numBytes = rowBytes * height;
		}
		else if (fmt == DXGI_FORMAT_NV11)
		{
			rowBytes = ((uint64_t(width) + 3u) >> 2) * 4u;
			numRows = uint64_t(height) * 2u; // Direct3D makes this simplifying assumption, although it is larger than the 4:1:1 data
			numBytes = rowBytes * numRows;
		}
		else if (planar)
		{
			rowBytes = ((uint64_t(width) + 1u) >> 1) * bpe;
			numBytes = (rowBytes * uint64_t(height)) + ((rowBytes * uint64_t(height) + 1u) >> 1);
			numRows = height + ((uint64_t(height) + 1u) >> 1);
		}
		else
		{
			size_t bpp = BitsPerPixel(fmt);
			if (!bpp)
				return E_INVALIDARG;

			rowBytes = (uint64_t(width) * bpp + 7u) / 8u; // round up to nearest byte
			numRows = uint64_t(height);
			numBytes = rowBytes * height;
		}

#if defined(_M_IX86) || defined(_M_ARM) || defined(_M_HYBRID_X86_ARM64)
		static_assert(sizeof(size_t) == 4, "Not a 32-bit platform!");
		if (numBytes > UINT32_MAX || rowBytes > UINT32_MAX || numRows > UINT32_MAX)
			return HRESULT_FROM_WIN32(ERROR_ARITHMETIC_OVERFLOW);
#else
		static_assert(sizeof(size_t) == 8, "Not a 64-bit platform!");
#endif

		if (outNumBytes)
		{
			*outNumBytes = static_cast<size_t>(numBytes);
		}
		if (outRowBytes)
		{
			*outRowBytes = static_cast<size_t>(rowBytes);
		}
		if (outNumRows)
		{
			*outNumRows = static_cast<size_t>(numRows);
		}

		return S_OK;
	}


	//--------------------------------------------------------------------------------------
#define ISBITMASK( r,g,b,a ) ( ddpf.RBitMask == r && ddpf.GBitMask == g && ddpf.BBitMask == b && ddpf.ABitMask == a )

	DXGI_FORMAT GetDXGIFormat(const DDS_PIXELFORMAT& ddpf)
	{
		if (ddpf.flags & DDS_RGB)
		{
			// Note that sRGB formats are written using the "DX10" extended header

			switch (ddpf.RGBBitCount)
			{
			case 32:
				if (ISBITMASK(0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000))
				{
					return DXGI_FORMAT_R8G8B8A8_UNORM;
				}

				if (ISBITMASK(0x00ff0000, 0x0000ff00, 0x000000ff, 0xff000000))
				{
					return DXGI_FORMAT_B8G8R8A8_UNORM;
				}

				if (ISBITMASK(0x00ff0000, 0x0000ff00, 0x000000ff, 0x00000000))
				{
					return DXGI_FORMAT_B8G8R8X8_UNORM;
				}

				// No DXGI format maps to ISBITMASK(0x000000ff,0x0000ff00,0x00ff0000,0x00000000) aka D3DFMT_X8B8G8R8

				// Note that many common DDS reader/writers (including D3DX) swap the
				// the RED/BLUE masks for 10:10:10:2 formats. We assume
				// below that the 'backwards' header mask is being used since it is most
				// likely written by D3DX. The more robust solution is to use the 'DX10'
				// header extension and specify the DXGI_FORMAT_R10G10B10A2_UNORM format directly

				// For 'correct' writers, this should be 0x000003ff,0x000ffc00,0x3ff00000 for RGB data
				if (ISBITMASK(0x3ff00000, 0x000ffc00, 0x000003ff, 0xc0000000))
				{
					return DXGI_FORMAT_R10G10B10A2_UNORM;
				}

				// No DXGI format maps to ISBITMASK(0x000003ff,0x000ffc00,0x3ff00000,0xc0000000) aka D3DFMT_A2R10G10B10

				if (ISBITMASK(0x0000ffff, 0xffff0000, 0x00000000, 0x00000000))
				{
					return DXGI_FORMAT_R16G16_UNORM;
				}

				if (ISBITMASK(0xffffffff, 0x00000000, 0x00000000, 0x00000000))
				{
					// Only 32-bit color channel format in D3D9 was R32F
					return DXGI_FORMAT_R32_FLOAT; // D3DX writes this out as a FourCC of 114
				}
				break;

			case 24:
				// No 24bpp DXGI formats aka D3DFMT_R8G8B8
				break;

			case 16:
				if (ISBITMASK(0x7c00, 0x03e0, 0x001f, 0x8000))
				{
					return DXGI_FORMAT_B5G5R5A1_UNORM;
				}
				if (ISBITMASK(0xf800, 0x07e0, 0x001f, 0x0000))
				{
					return DXGI_FORMAT_B5G6R5_UNORM;
				}

				// No DXGI format maps to ISBITMASK(0x7c00,0x03e0,0x001f,0x0000) aka D3DFMT_X1R5G5B5

				if (ISBITMASK(0x0f00, 0x00f0, 0x000f, 0xf000))
				{
					return DXGI_FORMAT_B4G4R4A4_UNORM;
				}

				// No DXGI format maps to ISBITMASK(0x0f00,0x00f0,0x000f,0x0000) aka D3DFMT_X4R4G4B4

				// No 3:3:2, 3:3:2:8, or paletted DXGI formats aka D3DFMT_A8R3G3B2, D3DFMT_R3G3B2, D3DFMT_P8, D3DFMT_A8P8, etc.
				break;
			}
		}
		else if (ddpf.flags & DDS_LUMINANCE)
		{
			if (8 == ddpf.RGBBitCount)
			{
				if (ISBITMASK(0x000000ff, 0x00000000, 0x00000000, 0x00000000))
				{
					return DXGI_FORMAT_R8_UNORM; // D3DX10/11 writes this out as DX10 extension
				}

				// No DXGI format maps to ISBITMASK(0x0f,0x00,0x00,0xf0) aka D3DFMT_A4L4

				if (ISBITMASK(0x000000ff, 0x00000000, 0x00000000, 0x0000ff00))
				{
					return DXGI_FORMAT_R8G8_UNORM; // Some DDS writers assume the bitcount should be 8 instead of 16
				}
			}

			if (16 == ddpf.RGBBitCount)
			{
				if (ISBITMASK(0x0000ffff, 0x00000000, 0x00000000, 0x00000000))
				{
					return DXGI_FORMAT_R16_UNORM; // D3DX10/11 writes this out as DX10 extension
				}
				if (ISBITMASK(0x000000ff, 0x00000000, 0x00000000, 0x0000ff00))
				{
					return DXGI_FORMAT_R8G8_UNORM; // D3DX10/11 writes this out as DX10 extension
				}
			}
		}
		else if (ddpf.flags & DDS_ALPHA)
		{
			if (8 == ddpf.RGBBitCount)
			{
				return DXGI_FORMAT_A8_UNORM;
			}
		}
		else if (ddpf.flags & DDS_BUMPDUDV)
		{
			if (16 == ddpf.RGBBitCount)
			{
				if (ISBITMASK(0x00ff, 0xff00, 0x0000, 0x0000))
				{
					return DXGI_FORMAT_R8G8_SNORM; // D3DX10/11 writes this out as DX10 extension
				}
			}

			if (32 == ddpf.RGBBitCount)
			{
				if (ISBITMASK(0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000))
				{
					return DXGI_FORMAT_R8G8B8A8_SNORM; // D3DX10/11 writes this out as DX10 extension
				}
				if (ISBITMASK(0x0000ffff, 0xffff0000, 0x00000000, 0x00000000))
				{
					return DXGI_FORMAT_R16G16_SNORM; // D3DX10/11 writes this out as DX10 extension
				}

				// No DXGI format maps to ISBITMASK(0x3ff00000, 0x000ffc00, 0x000003ff, 0xc0000000) aka D3DFMT_A2W10V10U10
			}
		}
		else if (ddpf.flags & DDS_FOURCC)
		{
			if (MAKEFOURCC('D', 'X', 'T', '1') == ddpf.fourCC)
			{
				return DXGI_FORMAT_BC1_UNORM;
			}
			if (MAKEFOURCC('D', 'X', 'T', '3') == ddpf.fourCC)
			{
				return DXGI_FORMAT_BC2_UNORM;
			}
			if (MAKEFOURCC('D', 'X', 'T', '5') == ddpf.fourCC)
			{
				return DXGI_FORMAT_BC3_UNORM;
			}

			// While pre-multiplied alpha isn't directly supported by the DXGI formats,
			// they are basically the same as these BC formats so they can be mapped
			if (MAKEFOURCC('D', 'X', 'T', '2') == ddpf.fourCC)
			{
				return DXGI_FORMAT_BC2_UNORM;
			}
			if (MAKEFOURCC('D', 'X', 'T', '4') == ddpf.fourCC)
			{
				return DXGI_FORMAT_BC3_UNORM;
			}

			if (MAKEFOURCC('A', 'T', 'I', '1') == ddpf.fourCC)
			{
				return DXGI_FORMAT_BC4_UNORM;
			}
			if (MAKEFOURCC('B', 'C', '4', 'U') == ddpf.fourCC)
			{
				return DXGI_FORMAT_BC4_UNORM;
			}
			if (MAKEFOURCC('B', 'C', '4', 'S') == ddpf.fourCC)
			{
				return DXGI_FORMAT_BC4_SNORM;
			}

			if (MAKEFOURCC('A', 'T', 'I', '2') == ddpf.fourCC)
			{
				return DXGI_FORMAT_BC5_UNORM;
			}
			if (MAKEFOURCC('B', 'C', '5', 'U') == ddpf.fourCC)
			{
				return DXGI_FORMAT_BC5_UNORM;
			}
			if (MAKEFOURCC('B', 'C', '5', 'S') == ddpf.fourCC)
			{
				return DXGI_FORMAT_BC5_SNORM;
			}

			// BC6H and BC7 are written using the "DX10" extended header

			if (MAKEFOURCC('R', 'G', 'B', 'G') == ddpf.fourCC)
			{
				return DXGI_FORMAT_R8G8_B8G8_UNORM;
			}
			if (MAKEFOURCC('G', 'R', 'G', 'B') == ddpf.fourCC)
			{
				return DXGI_FORMAT_G8R8_G8B8_UNORM;
			}

			if (MAKEFOURCC('Y', 'U', 'Y', '2') == ddpf.fourCC)
			{
				return DXGI_FORMAT_YUY2;
			}

			// Check for D3DFORMAT enums being set here
			switch (ddpf.fourCC)
			{
			case 36: // D3DFMT_A16B16G16R16
				return DXGI_FORMAT_R16G16B16A16_UNORM;

			case 110: // D3DFMT_Q16W16V16U16
				return DXGI_FORMAT_R16G16B16A16_SNORM;

			case 111: // D3DFMT_R16F
				return DXGI_FORMAT_R16_FLOAT;

			case 112: // D3DFMT_G16R16F
				return DXGI_FORMAT_R16G16_FLOAT;

			case 113: // D3DFMT_A16B16G16R16F
				return DXGI_FORMAT_R16G16B16A16_FLOAT;

			case 114: // D3DFMT_R32F
				return DXGI_FORMAT_R32_FLOAT;

			case 115: // D3DFMT_G32R32F
				return DXGI_FORMAT_R32G32_FLOAT;

			case 116: // D3DFMT_A32B32G32R32F
				return DXGI_FORMAT_R32G32B32A32_FLOAT;
			}
		}

		return DXGI_FORMAT_UNKNOWN;
	}


	//--------------------------------------------------------------------------------------
	DXGI_FORMAT MakeSRGB(_In_ DXGI_FORMAT format)
	{
		switch (format)
		{
		case DXGI_FORMAT_R8G8B8A8_UNORM:
			return DXGI_FORMAT_R8G8B8A8_UNORM_SRGB;

		case DXGI_FORMAT_BC1_UNORM:
			return DXGI_FORMAT_BC1_UNORM_SRGB;

		case DXGI_FORMAT_BC2_UNORM:
			return DXGI_FORMAT_BC2_UNORM_SRGB;

		case DXGI_FORMAT_BC3_UNORM:
			return DXGI_FORMAT_BC3_UNORM_SRGB;

		case DXGI_FORMAT_B8G8R8A8_UNORM:
			return DXGI_FORMAT_B8G8R8A8_UNORM_SRGB;

		case DXGI_FORMAT_B8G8R8X8_UNORM:
			return DXGI_FORMAT_B8G8R8X8_UNORM_SRGB;

		case DXGI_FORMAT_BC7_UNORM:
			return DXGI_FORMAT_BC7_UNORM_SRGB;

		default:
			return format;
		}
	}


	//--------------------------------------------------------------------------------------
	HRESULT FillInitData(
		_In_ size_t width,
		_In_ size_t height,
		_In_ size_t depth,
		_In_ size_t mipCount,
		_In_ size_t arraySize,
		_In_ DXGI_FORMAT format,
		_In_ size_t maxsize,
		_In_ size_t bitSize,
		_In_reads_bytes_(bitSize) const uint8_t* bitData,
		_Out_ size_t& twidth,
		_Out_ size_t& theight,
		_Out_ size_t& tdepth,
		_Out_ size_t& skipMip,
		_Out_writes_(mipCount*arraySize) D3D11_SUBRESOURCE_DATA* initData)
	{
		if (!bitData || !initData)
		{
			return E_POINTER;
		}

		skipMip = 0;
		twidth = 0;
		theight = 0;
		tdepth = 0;

		size_t NumBytes = 0;
		size_t RowBytes = 0;
		const uint8_t* pSrcBits = bitData;
		const uint8_t* pEndBits = bitData + bitSize;

		size_t index = 0;
		for (size_t j = 0; j < arraySize; j++)
		{
			size_t w = width;
			size_t h = height;
			size_t d = depth;
			for (size_t i = 0; i < mipCount; i++)
			{
				HRESULT hr = GetSurfaceInfo(w, h, format, &NumBytes, &RowBytes, nullptr);
				if (FAILED(hr))
					return hr;

				if (NumBytes > UINT32_MAX || RowBytes > UINT32_MAX)
					return HRESULT_FROM_WIN32(ERROR_ARITHMETIC_OVERFLOW);

				if ((mipCount <= 1) || !maxsize || (w <= maxsize && h <= maxsize && d <= maxsize))
				{
					if (!twidth)
					{
						twidth = w;
						theight = h;
						tdepth = d;
					}

					assert(index < mipCount * arraySize);
					_Analysis_assume_(index < mipCount * arraySize);
					initData[index].pSysMem = (const void*)pSrcBits;
					initData[index].SysMemPitch = static_cast<UINT>(RowBytes);
					initData[index].SysMemSlicePitch = static_cast<UINT>(NumBytes);
					++index;
				}
				else if (!j)
				{
					// Count number of skipped mipmaps (first item only)
					++skipMip;
				}

				if (pSrcBits + (NumBytes*d) > pEndBits)
				{
					return HRESULT_FROM_WIN32(ERROR_HANDLE_EOF);
				}

				pSrcBits += NumBytes * d;

				w = w >> 1;
				h = h >> 1;
				d = d >> 1;
				if (w == 0)
				{
					w = 1;
				}
				if (h == 0)
				{
					h = 1;
				}
				if (d == 0)
				{
					d = 1;
				}
			}
		}

		return (index > 0) ? S_OK : E_FAIL;
	}


	//--------------------------------------------------------------------------------------
	HRESULT CreateD3DResources(
		_In_ ID3D11Device* d3dDevice,
		_In_ uint32_t resDim,
		_In_ size_t width,
		_In_ size_t height,
		_In_ size_t depth,
		_In_ size_t mipCount,
		_In_ size_t arraySize,
		_In_ DXGI_FORMAT format,
		_In_ D3D11_USAGE usage,
		_In_ unsigned int bindFlags,
		_In_ unsigned int cpuAccessFlags,
		_In_ unsigned int miscFlags,
		_In_ bool forceSRGB,
		_In_ bool isCubeMap,
		_In_reads_opt_(mipCount*arraySize) D3D11_SUBRESOURCE_DATA* initData,
		_Outptr_opt_ ID3D11Resource** texture,
		_Outptr_opt_ ID3D11ShaderResourceView** textureView)
	{
		if (!d3dDevice)
			return E_POINTER;

		HRESULT hr = E_FAIL;

		if (forceSRGB)
		{
			format = MakeSRGB(format);
		}

		switch (resDim)
		{
		case D3D11_RESOURCE_DIMENSION_TEXTURE1D:
		{
			D3D11_TEXTURE1D_DESC desc;
			desc.Width = static_cast<UINT>(width);
			desc.MipLevels = static_cast<UINT>(mipCount);
			desc.ArraySize = static_cast<UINT>(arraySize);
			desc.Format = format;
			desc.Usage = usage;
			desc.BindFlags = bindFlags;
			desc.CPUAccessFlags = cpuAccessFlags;
			desc.MiscFlags = miscFlags & ~D3D11_RESOURCE_MISC_TEXTURECUBE;

			ID3D11Texture1D* tex = nullptr;
			hr = d3dDevice->CreateTexture1D(&desc,
				initData,
				&tex
			);
			if (SUCCEEDED(hr) && tex)
			{
				if (textureView)
				{
					D3D11_SHADER_RESOURCE_VIEW_DESC SRVDesc = {};
					SRVDesc.Format = format;

					if (arraySize > 1)
					{
						SRVDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE1DARRAY;
						SRVDesc.Texture1DArray.MipLevels = (!mipCount) ? -1 : desc.MipLevels;
						SRVDesc.Texture1DArray.ArraySize = static_cast<UINT>(arraySize);
					}
					else
					{
						SRVDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE1D;
						SRVDesc.Texture1D.MipLevels = (!mipCount) ? -1 : desc.MipLevels;
					}

					hr = d3dDevice->CreateShaderResourceView(tex,
						&SRVDesc,
						textureView
					);
					if (FAILED(hr))
					{
						tex->Release();
						return hr;
					}
				}

				if (texture)
				{
					*texture = tex;
				}
				else
				{
					SetDebugObjectName(tex, "DDSTextureLoader");
					tex->Release();
				}
			}
		}
		break;

		case D3D11_RESOURCE_DIMENSION_TEXTURE2D:
		{
			D3D11_TEXTURE2D_DESC desc;
			desc.Width = static_cast<UINT>(width);
			desc.Height = static_cast<UINT>(height);
			desc.MipLevels = static_cast<UINT>(mipCount);
			desc.ArraySize = static_cast<UINT>(arraySize);
			desc.Format = format;
			desc.SampleDesc.Count = 1;
			desc.SampleDesc.Quality = 0;
			desc.Usage = usage;
			desc.BindFlags = bindFlags;
			desc.CPUAccessFlags = cpuAccessFlags;
			if (isCubeMap)
			{
				desc.MiscFlags = miscFlags | D3D11_RESOURCE_MISC_TEXTURECUBE;
			}
			else
			{
				desc.MiscFlags = miscFlags & ~D3D11_RESOURCE_MISC_TEXTURECUBE;
			}

			ID3D11Texture2D* tex = nullptr;
			hr = d3dDevice->CreateTexture2D(&desc,
				initData,
				&tex
			);
			if (SUCCEEDED(hr) && tex)
			{
				if (textureView)
				{
					D3D11_SHADER_RESOURCE_VIEW_DESC SRVDesc = {};
					SRVDesc.Format = format;

					if (isCubeMap)
					{
						if (arraySize > 6)
						{
							SRVDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURECUBEARRAY;
							SRVDesc.TextureCubeArray.MipLevels = (!mipCount) ? -1 : desc.MipLevels;

							// Earlier we set arraySize to (NumCubes * 6)
							SRVDesc.TextureCubeArray.NumCubes = static_cast<UINT>(arraySize / 6);
						}
						else
						{
							SRVDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURECUBE;
							SRVDesc.TextureCube.MipLevels = (!mipCount) ? -1 : desc.MipLevels;
						}
					}
					else if (arraySize > 1)
					{
						SRVDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2DARRAY;
						SRVDesc.Texture2DArray.MipLevels = (!mipCount) ? -1 : desc.MipLevels;
						SRVDesc.Texture2DArray.ArraySize = static_cast<UINT>(arraySize);
					}
					else
					{
						SRVDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D;
						SRVDesc.Texture2D.MipLevels = (!mipCount) ? -1 : desc.MipLevels;
					}

					hr = d3dDevice->CreateShaderResourceView(tex,
						&SRVDesc,
						textureView
					);
					if (FAILED(hr))
					{
						tex->Release();
						return hr;
					}
				}

				if (texture)
				{
					*texture = tex;
				}
				else
				{
					SetDebugObjectName(tex, "DDSTextureLoader");
					tex->Release();
				}
			}
		}
		break;

		case D3D11_RESOURCE_DIMENSION_TEXTURE3D:
		{
			D3D11_TEXTURE3D_DESC desc;
			desc.Width = static_cast<UINT>(width);
			desc.Height = static_cast<UINT>(height);
			desc.Depth = static_cast<UINT>(depth);
			desc.MipLevels = static_cast<UINT>(mipCount);
			desc.Format = format;
			desc.Usage = usage;
			desc.BindFlags = bindFlags;
			desc.CPUAccessFlags = cpuAccessFlags;
			desc.MiscFlags = miscFlags & ~D3D11_RESOURCE_MISC_TEXTURECUBE;

			ID3D11Texture3D* tex = nullptr;
			hr = d3dDevice->CreateTexture3D(&desc,
				initData,
				&tex
			);
			if (SUCCEEDED(hr) && tex)
			{
				if (textureView)
				{
					D3D11_SHADER_RESOURCE_VIEW_DESC SRVDesc = {};
					SRVDesc.Format = format;

					SRVDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE3D;
					SRVDesc.Texture3D.MipLevels = (!mipCount) ? -1 : desc.MipLevels;

					hr = d3dDevice->CreateShaderResourceView(tex,
						&SRVDesc,
						textureView
					);
					if (FAILED(hr))
					{
						tex->Release();
						return hr;
					}
				}

				if (texture)
				{
					*texture = tex;
				}
				else
				{
					SetDebugObjectName(tex, "DDSTextureLoader");
					tex->Release();
				}
			}
		}
		break;
		}

		return hr;
	}


	//--------------------------------------------------------------------------------------
	HRESULT CreateTextureFromDDS(
		_In_ ID3D11Device* d3dDevice,
		_In_opt_ ID3D11DeviceContext* d3dContext,
		_In_ const DDS_HEADER* header,
		_In_reads_bytes_(bitSize) const uint8_t* bitData,
		_In_ size_t bitSize,
		_In_ size_t maxsize,
		_In_ D3D11_USAGE usage,
		_In_ unsigned int bindFlags,
		_In_ unsigned int cpuAccessFlags,
		_In_ unsigned int miscFlags,
		_In_ bool forceSRGB,
		_Outptr_opt_ ID3D11Resource** texture,
		_Outptr_opt_ ID3D11ShaderResourceView** textureView)
	{
		HRESULT hr = S_OK;

		UINT width = header->width;
		UINT height = header->height;
		UINT depth = header->depth;

		uint32_t resDim = D3D11_RESOURCE_DIMENSION_UNKNOWN;
		UINT arraySize = 1;
		DXGI_FORMAT format = DXGI_FORMAT_UNKNOWN;
		bool isCubeMap = false;

		size_t mipCount = header->mipMapCount;
		if (0 == mipCount)
		{
			mipCount = 1;
		}

		if ((header->ddspf.flags & DDS_FOURCC) &&
			(MAKEFOURCC('D', 'X', '1', '0') == header->ddspf.fourCC))
		{
			auto d3d10ext = reinterpret_cast<const DDS_HEADER_DXT10*>((const char*)header + sizeof(DDS_HEADER));

			arraySize = d3d10ext->arraySize;
			if (arraySize == 0)
			{
				return HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
			}

			switch (d3d10ext->dxgiFormat)
			{
			case DXGI_FORMAT_AI44:
			case DXGI_FORMAT_IA44:
			case DXGI_FORMAT_P8:
			case DXGI_FORMAT_A8P8:
				return HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED);

			default:
				if (BitsPerPixel(d3d10ext->dxgiFormat) == 0)
				{
					return HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED);
				}
			}

			format = d3d10ext->dxgiFormat;

			switch (d3d10ext->resourceDimension)
			{
			case D3D11_RESOURCE_DIMENSION_TEXTURE1D:
				// D3DX writes 1D textures with a fixed Height of 1
				if ((header->flags & DDS_HEIGHT) && height != 1)
				{
					return HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
				}
				height = depth = 1;
				break;

			case D3D11_RESOURCE_DIMENSION_TEXTURE2D:
				if (d3d10ext->miscFlag & D3D11_RESOURCE_MISC_TEXTURECUBE)
				{
					arraySize *= 6;
					isCubeMap = true;
				}
				depth = 1;
				break;

			case D3D11_RESOURCE_DIMENSION_TEXTURE3D:
				if (!(header->flags & DDS_HEADER_FLAGS_VOLUME))
				{
					return HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
				}

				if (arraySize > 1)
				{
					return HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED);
				}
				break;

			default:
				return HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED);
			}

			resDim = d3d10ext->resourceDimension;
		}
		else
		{
			format = GetDXGIFormat(header->ddspf);

			if (format == DXGI_FORMAT_UNKNOWN)
			{
				return HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED);
			}

			if (header->flags & DDS_HEADER_FLAGS_VOLUME)
			{
				resDim = D3D11_RESOURCE_DIMENSION_TEXTURE3D;
			}
			else
			{
				if (header->caps2 & DDS_CUBEMAP)
				{
					// We require all six faces to be defined
					if ((header->caps2 & DDS_CUBEMAP_ALLFACES) != DDS_CUBEMAP_ALLFACES)
					{
						return HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED);
					}

					arraySize = 6;
					isCubeMap = true;
				}

				depth = 1;
				resDim = D3D11_RESOURCE_DIMENSION_TEXTURE2D;

				// Note there's no way for a legacy Direct3D 9 DDS to express a '1D' texture
			}

			assert(BitsPerPixel(format) != 0);
		}

		// Bound sizes (for security purposes we don't trust DDS file metadata larger than the D3D 11.x hardware requirements)
		if (mipCount > D3D11_REQ_MIP_LEVELS)
		{
			return HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED);
		}

		switch (resDim)
		{
		case D3D11_RESOURCE_DIMENSION_TEXTURE1D:
			if ((arraySize > D3D11_REQ_TEXTURE1D_ARRAY_AXIS_DIMENSION) ||
				(width > D3D11_REQ_TEXTURE1D_U_DIMENSION))
			{
				return HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED);
			}
			break;

		case D3D11_RESOURCE_DIMENSION_TEXTURE2D:
			if (isCubeMap)
			{
				// This is the right bound because we set arraySize to (NumCubes*6) above
				if ((arraySize > D3D11_REQ_TEXTURE2D_ARRAY_AXIS_DIMENSION) ||
					(width > D3D11_REQ_TEXTURECUBE_DIMENSION) ||
					(height > D3D11_REQ_TEXTURECUBE_DIMENSION))
				{
					return HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED);
				}
			}
			else if ((arraySize > D3D11_REQ_TEXTURE2D_ARRAY_AXIS_DIMENSION) ||
				(width > D3D11_REQ_TEXTURE2D_U_OR_V_DIMENSION) ||
				(height > D3D11_REQ_TEXTURE2D_U_OR_V_DIMENSION))
			{
				return HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED);
			}
			break;

		case D3D11_RESOURCE_DIMENSION_TEXTURE3D:
			if ((arraySize > 1) ||
				(width > D3D11_REQ_TEXTURE3D_U_V_OR_W_DIMENSION) ||
				(height > D3D11_REQ_TEXTURE3D_U_V_OR_W_DIMENSION) ||
				(depth > D3D11_REQ_TEXTURE3D_U_V_OR_W_DIMENSION))
			{
				return HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED);
			}
			break;

		default:
			return HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED);
		}

		bool autogen = false;
		if (mipCount == 1 && d3dContext && textureView) // Must have context and shader-view to auto generate mipmaps
		{
			// See if format is supported for auto-gen mipmaps (varies by feature level)
			UINT fmtSupport = 0;
			hr = d3dDevice->CheckFormatSupport(format, &fmtSupport);
			if (SUCCEEDED(hr) && (fmtSupport & D3D11_FORMAT_SUPPORT_MIP_AUTOGEN))
			{
				// 10level9 feature levels do not support auto-gen mipgen for volume textures
				if ((resDim != D3D11_RESOURCE_DIMENSION_TEXTURE3D)
					|| (d3dDevice->GetFeatureLevel() >= D3D_FEATURE_LEVEL_10_0))
				{
					autogen = true;
				}
			}
		}

		if (autogen)
		{
			// Create texture with auto-generated mipmaps
			ID3D11Resource* tex = nullptr;
			hr = CreateD3DResources(d3dDevice, resDim, width, height, depth, 0, arraySize,
				format, usage,
				bindFlags | D3D11_BIND_RENDER_TARGET,
				cpuAccessFlags,
				miscFlags | D3D11_RESOURCE_MISC_GENERATE_MIPS, forceSRGB,
				isCubeMap, nullptr, &tex, textureView);
			if (SUCCEEDED(hr))
			{
				size_t numBytes = 0;
				size_t rowBytes = 0;
				hr = GetSurfaceInfo(width, height, format, &numBytes, &rowBytes, nullptr);
				if (FAILED(hr))
					return hr;

				if (numBytes > bitSize)
				{
					(*textureView)->Release();
					*textureView = nullptr;
					tex->Release();
					return HRESULT_FROM_WIN32(ERROR_HANDLE_EOF);
				}

				if (numBytes > UINT32_MAX || rowBytes > UINT32_MAX)
					return HRESULT_FROM_WIN32(ERROR_ARITHMETIC_OVERFLOW);

				D3D11_SHADER_RESOURCE_VIEW_DESC desc;
				(*textureView)->GetDesc(&desc);

				UINT mipLevels = 1;

				switch (desc.ViewDimension)
				{
				case D3D_SRV_DIMENSION_TEXTURE1D:       mipLevels = desc.Texture1D.MipLevels; break;
				case D3D_SRV_DIMENSION_TEXTURE1DARRAY:  mipLevels = desc.Texture1DArray.MipLevels; break;
				case D3D_SRV_DIMENSION_TEXTURE2D:       mipLevels = desc.Texture2D.MipLevels; break;
				case D3D_SRV_DIMENSION_TEXTURE2DARRAY:  mipLevels = desc.Texture2DArray.MipLevels; break;
				case D3D_SRV_DIMENSION_TEXTURECUBE:     mipLevels = desc.TextureCube.MipLevels; break;
				case D3D_SRV_DIMENSION_TEXTURECUBEARRAY:mipLevels = desc.TextureCubeArray.MipLevels; break;
				case D3D_SRV_DIMENSION_TEXTURE3D:       mipLevels = desc.Texture3D.MipLevels; break;
				default:
					(*textureView)->Release();
					*textureView = nullptr;
					tex->Release();
					return E_UNEXPECTED;
				}

				if (arraySize > 1)
				{
					const uint8_t* pSrcBits = bitData;
					const uint8_t* pEndBits = bitData + bitSize;
					for (UINT item = 0; item < arraySize; ++item)
					{
						if ((pSrcBits + numBytes) > pEndBits)
						{
							(*textureView)->Release();
							*textureView = nullptr;
							tex->Release();
							return HRESULT_FROM_WIN32(ERROR_HANDLE_EOF);
						}

						UINT res = D3D11CalcSubresource(0, item, mipLevels);
						d3dContext->UpdateSubresource(tex, res, nullptr, pSrcBits, static_cast<UINT>(rowBytes), static_cast<UINT>(numBytes));
						pSrcBits += numBytes;
					}
				}
				else
				{
					d3dContext->UpdateSubresource(tex, 0, nullptr, bitData, static_cast<UINT>(rowBytes), static_cast<UINT>(numBytes));
				}

				d3dContext->GenerateMips(*textureView);

				if (texture)
				{
					*texture = tex;
				}
				else
				{
					tex->Release();
				}
			}
		}
		else
		{
			// Create the texture
			std::unique_ptr<D3D11_SUBRESOURCE_DATA[]> initData(new (std::nothrow) D3D11_SUBRESOURCE_DATA[mipCount * arraySize]);
			if (!initData)
			{
				return E_OUTOFMEMORY;
			}

			size_t skipMip = 0;
			size_t twidth = 0;
			size_t theight = 0;
			size_t tdepth = 0;
			hr = FillInitData(width, height, depth, mipCount, arraySize, format, maxsize, bitSize, bitData,
				twidth, theight, tdepth, skipMip, initData.get());

			if (SUCCEEDED(hr))
			{
				hr = CreateD3DResources(d3dDevice, resDim, twidth, theight, tdepth, mipCount - skipMip, arraySize,
					format, usage, bindFlags, cpuAccessFlags, miscFlags, forceSRGB,
					isCubeMap, initData.get(), texture, textureView);

				if (FAILED(hr) && !maxsize && (mipCount > 1))
				{
					// Retry with a maxsize determined by feature level
					switch (d3dDevice->GetFeatureLevel())
					{
					case D3D_FEATURE_LEVEL_9_1:
					case D3D_FEATURE_LEVEL_9_2:
						if (isCubeMap)
						{
							maxsize = 512 /*D3D_FL9_1_REQ_TEXTURECUBE_DIMENSION*/;
						}
						else
						{
							maxsize = (resDim == D3D11_RESOURCE_DIMENSION_TEXTURE3D)
								? 256 /*D3D_FL9_1_REQ_TEXTURE3D_U_V_OR_W_DIMENSION*/
								: 2048 /*D3D_FL9_1_REQ_TEXTURE2D_U_OR_V_DIMENSION*/;
						}
						break;

					case D3D_FEATURE_LEVEL_9_3:
						maxsize = (resDim == D3D11_RESOURCE_DIMENSION_TEXTURE3D)
							? 256 /*D3D_FL9_1_REQ_TEXTURE3D_U_V_OR_W_DIMENSION*/
							: 4096 /*D3D_FL9_3_REQ_TEXTURE2D_U_OR_V_DIMENSION*/;
						break;

					default: // D3D_FEATURE_LEVEL_10_0 & D3D_FEATURE_LEVEL_10_1
						maxsize = (resDim == D3D11_RESOURCE_DIMENSION_TEXTURE3D)
							? 2048 /*D3D10_REQ_TEXTURE3D_U_V_OR_W_DIMENSION*/
							: 8192 /*D3D10_REQ_TEXTURE2D_U_OR_V_DIMENSION*/;
						break;
					}

					hr = FillInitData(width, height, depth, mipCount, arraySize, format, maxsize, bitSize, bitData,
						twidth, theight, tdepth, skipMip, initData.get());
					if (SUCCEEDED(hr))
					{
						hr = CreateD3DResources(d3dDevice, resDim, twidth, theight, tdepth, mipCount - skipMip, arraySize,
							format, usage, bindFlags, cpuAccessFlags, miscFlags, forceSRGB,
							isCubeMap, initData.get(), texture, textureView);
					}
				}
			}
		}

		return hr;
	}


	//--------------------------------------------------------------------------------------
	DDS_ALPHA_MODE GetAlphaMode(_In_ const DDS_HEADER* header)
	{
		if (header->ddspf.flags & DDS_FOURCC)
		{
			if (MAKEFOURCC('D', 'X', '1', '0') == header->ddspf.fourCC)
			{
				auto d3d10ext = reinterpret_cast<const DDS_HEADER_DXT10*>((const char*)header + sizeof(DDS_HEADER));
				auto mode = static_cast<DDS_ALPHA_MODE>(d3d10ext->miscFlags2 & DDS_MISC_FLAGS2_ALPHA_MODE_MASK);
				switch (mode)
				{
				case DDS_ALPHA_MODE_STRAIGHT:
				case DDS_ALPHA_MODE_PREMULTIPLIED:
				case DDS_ALPHA_MODE_OPAQUE:
				case DDS_ALPHA_MODE_CUSTOM:
					return mode;
				}
			}
			else if ((MAKEFOURCC('D', 'X', 'T', '2') == header->ddspf.fourCC)
				|| (MAKEFOURCC('D', 'X', 'T', '4') == header->ddspf.fourCC))
			{
				return DDS_ALPHA_MODE_PREMULTIPLIED;
			}
		}

		return DDS_ALPHA_MODE_UNKNOWN;
	}
} // anonymous namespace

//--------------------------------------------------------------------------------------
_Use_decl_annotations_
HRESULT DirectX::CreateDDSTextureFromMemory(ID3D11Device* d3dDevice,
	const uint8_t* ddsData,
	size_t ddsDataSize,
	ID3D11Resource** texture,
	ID3D11ShaderResourceView** textureView,
	size_t maxsize,
	DDS_ALPHA_MODE* alphaMode)
{
	return CreateDDSTextureFromMemoryEx(d3dDevice, nullptr, ddsData, ddsDataSize, maxsize,
		D3D11_USAGE_DEFAULT, D3D11_BIND_SHADER_RESOURCE, 0, 0, false,
		texture, textureView, alphaMode);
}

_Use_decl_annotations_
HRESULT DirectX::CreateDDSTextureFromMemory(ID3D11Device* d3dDevice,
	ID3D11DeviceContext* d3dContext,
	const uint8_t* ddsData,
	size_t ddsDataSize,
	ID3D11Resource** texture,
	ID3D11ShaderResourceView** textureView,
	size_t maxsize,
	DDS_ALPHA_MODE* alphaMode)
{
	return CreateDDSTextureFromMemoryEx(d3dDevice, d3dContext, ddsData, ddsDataSize, maxsize,
		D3D11_USAGE_DEFAULT, D3D11_BIND_SHADER_RESOURCE, 0, 0, false,
		texture, textureView, alphaMode);
}

_Use_decl_annotations_
HRESULT DirectX::CreateDDSTextureFromMemoryEx(ID3D11Device* d3dDevice,
	const uint8_t* ddsData,
	size_t ddsDataSize,
	size_t maxsize,
	D3D11_USAGE usage,
	unsigned int bindFlags,
	unsigned int cpuAccessFlags,
	unsigned int miscFlags,
	bool forceSRGB,
	ID3D11Resource** texture,
	ID3D11ShaderResourceView** textureView,
	DDS_ALPHA_MODE* alphaMode)
{
	return CreateDDSTextureFromMemoryEx(d3dDevice, nullptr, ddsData, ddsDataSize, maxsize,
		usage, bindFlags, cpuAccessFlags, miscFlags, forceSRGB,
		texture, textureView, alphaMode);
}

_Use_decl_annotations_
HRESULT DirectX::CreateDDSTextureFromMemoryEx(ID3D11Device* d3dDevice,
	ID3D11DeviceContext* d3dContext,
	const uint8_t* ddsData,
	size_t ddsDataSize,
	size_t maxsize,
	D3D11_USAGE usage,
	unsigned int bindFlags,
	unsigned int cpuAccessFlags,
	unsigned int miscFlags,
	bool forceSRGB,
	ID3D11Resource** texture,
	ID3D11ShaderResourceView** textureView,
	DDS_ALPHA_MODE* alphaMode)
{
	if (texture)
	{
		*texture = nullptr;
	}
	if (textureView)
	{
		*textureView = nullptr;
	}
	if (alphaMode)
	{
		*alphaMode = DDS_ALPHA_MODE_UNKNOWN;
	}

	if (!d3dDevice || !ddsData || (!texture && !textureView))
	{
		return E_INVALIDARG;
	}

	// Validate DDS file in memory
	if (ddsDataSize < (sizeof(uint32_t) + sizeof(DDS_HEADER)))
	{
		return E_FAIL;
	}

	uint32_t dwMagicNumber = *(const uint32_t*)(ddsData);
	if (dwMagicNumber != DDS_MAGIC)
	{
		return E_FAIL;
	}

	auto header = reinterpret_cast<const DDS_HEADER*>(ddsData + sizeof(uint32_t));

	// Verify header to validate DDS file
	if (header->size != sizeof(DDS_HEADER) ||
		header->ddspf.size != sizeof(DDS_PIXELFORMAT))
	{
		return E_FAIL;
	}

	// Check for DX10 extension
	bool bDXT10Header = false;
	if ((header->ddspf.flags & DDS_FOURCC) &&
		(MAKEFOURCC('D', 'X', '1', '0') == header->ddspf.fourCC))
	{
		// Must be long enough for both headers and magic value
		if (ddsDataSize < (sizeof(DDS_HEADER) + sizeof(uint32_t) + sizeof(DDS_HEADER_DXT10)))
		{
			return E_FAIL;
		}

		bDXT10Header = true;
	}

	ptrdiff_t offset = sizeof(uint32_t)
		+ sizeof(DDS_HEADER)
		+ (bDXT10Header ? sizeof(DDS_HEADER_DXT10) : 0);

	HRESULT hr = CreateTextureFromDDS(d3dDevice, d3dContext, header,
		ddsData + offset, ddsDataSize - offset, maxsize,
		usage, bindFlags, cpuAccessFlags, miscFlags, forceSRGB,
		texture, textureView);
	if (SUCCEEDED(hr))
	{
		if (texture && *texture)
		{
			SetDebugObjectName(*texture, "DDSTextureLoader");
		}

		if (textureView && *textureView)
		{
			SetDebugObjectName(*textureView, "DDSTextureLoader");
		}

		if (alphaMode)
			*alphaMode = GetAlphaMode(header);
	}

	return hr;
}

//--------------------------------------------------------------------------------------
_Use_decl_annotations_
HRESULT DirectX::CreateDDSTextureFromFile(ID3D11Device* d3dDevice,
	const wchar_t* fileName,
	ID3D11Resource** texture,
	ID3D11ShaderResourceView** textureView,
	size_t maxsize,
	DDS_ALPHA_MODE* alphaMode)
{
	return CreateDDSTextureFromFileEx(d3dDevice, nullptr, fileName, maxsize,
		D3D11_USAGE_DEFAULT, D3D11_BIND_SHADER_RESOURCE, 0, 0, false,
		texture, textureView, alphaMode);
}

_Use_decl_annotations_
HRESULT DirectX::CreateDDSTextureFromFile(ID3D11Device* d3dDevice,
	ID3D11DeviceContext* d3dContext,
	const wchar_t* fileName,
	ID3D11Resource** texture,
	ID3D11ShaderResourceView** textureView,
	size_t maxsize,
	DDS_ALPHA_MODE* alphaMode)
{
	return CreateDDSTextureFromFileEx(d3dDevice, d3dContext, fileName, maxsize,
		D3D11_USAGE_DEFAULT, D3D11_BIND_SHADER_RESOURCE, 0, 0, false,
		texture, textureView, alphaMode);
}

_Use_decl_annotations_
HRESULT DirectX::CreateDDSTextureFromFileEx(ID3D11Device* d3dDevice,
	const wchar_t* fileName,
	size_t maxsize,
	D3D11_USAGE usage,
	unsigned int bindFlags,
	unsigned int cpuAccessFlags,
	unsigned int miscFlags,
	bool forceSRGB,
	ID3D11Resource** texture,
	ID3D11ShaderResourceView** textureView,
	DDS_ALPHA_MODE* alphaMode)
{
	return CreateDDSTextureFromFileEx(d3dDevice, nullptr, fileName, maxsize,
		usage, bindFlags, cpuAccessFlags, miscFlags, forceSRGB,
		texture, textureView, alphaMode);
}

_Use_decl_annotations_
HRESULT DirectX::CreateDDSTextureFromFileEx(ID3D11Device* d3dDevice,
	ID3D11DeviceContext* d3dContext,
	const wchar_t* fileName,
	size_t maxsize,
	D3D11_USAGE usage,
	unsigned int bindFlags,
	unsigned int cpuAccessFlags,
	unsigned int miscFlags,
	bool forceSRGB,
	ID3D11Resource** texture,
	ID3D11ShaderResourceView** textureView,
	DDS_ALPHA_MODE* alphaMode)
{
	if (texture)
	{
		*texture = nullptr;
	}
	if (textureView)
	{
		*textureView = nullptr;
	}
	if (alphaMode)
	{
		*alphaMode = DDS_ALPHA_MODE_UNKNOWN;
	}

	if (!d3dDevice || !fileName || (!texture && !textureView))
	{
		return E_INVALIDARG;
	}

	const DDS_HEADER* header = nullptr;
	const uint8_t* bitData = nullptr;
	size_t bitSize = 0;

	std::unique_ptr<uint8_t[]> ddsData;
	HRESULT hr = LoadTextureDataFromFile(fileName,
		ddsData,
		&header,
		&bitData,
		&bitSize
	);
	if (FAILED(hr))
	{
		return hr;
	}

	hr = CreateTextureFromDDS(d3dDevice, d3dContext, header,
		bitData, bitSize, maxsize,
		usage, bindFlags, cpuAccessFlags, miscFlags, forceSRGB,
		texture, textureView);

	if (SUCCEEDED(hr))
	{
#if !defined(NO_D3D11_DEBUG_NAME) && ( defined(_DEBUG) || defined(PROFILE) )
		if (texture || textureView)
		{
			CHAR strFileA[MAX_PATH];
			int result = WideCharToMultiByte(CP_UTF8,
				WC_NO_BEST_FIT_CHARS,
				fileName,
				-1,
				strFileA,
				MAX_PATH,
				nullptr,
				FALSE
			);
			if (result > 0)
			{
				const CHAR* pstrName = strrchr(strFileA, '\\');
				if (!pstrName)
				{
					pstrName = strFileA;
				}
				else
				{
					pstrName++;
				}

				if (texture && *texture)
				{
					(*texture)->SetPrivateData(WKPDID_D3DDebugObjectName,
						static_cast<UINT>(strnlen_s(pstrName, MAX_PATH)),
						pstrName
					);
				}

				if (textureView && *textureView)
				{
					(*textureView)->SetPrivateData(WKPDID_D3DDebugObjectName,
						static_cast<UINT>(strnlen_s(pstrName, MAX_PATH)),
						pstrName
					);
				}
			}
		}
#endif

		if (alphaMode)
			*alphaMode = GetAlphaMode(header);
	}

	return hr;
}
